import * as formidable from "formidable";
import {Files} from "formidable";
import {Fields} from "formidable";
import http = require("http");
import {FileMultiple, IncomingFormParse, exp, IFieldRes} from "./exp";
import  * as fs from "fs"
import {Request} from "express";
import {sdf, MapString} from "./public/sdf";
import {Log} from "./Log";
import {ExpressExt} from "./ExpressExt";

declare module "formidable" {
    export interface IncomingForm {
        parsePromise(req: http.IncomingMessage): Promise<FormParse>;
        /**
         * 解析表单,并将字段与文件结果合并
         */
        parseModPromise(req: Request): Promise<MapString<string|FileMultiple>>;
        /**
         * 删除所有上传的文件
         */
        removeFileList();
    }

}


declare module "express" {
    export interface Request {
        multipart: FormParse;
    }
}


formidable.IncomingForm.prototype.removeFileList = function () {
    if (this.fileList) {
        (this.fileList as FileMultiple[]).forEach(it=> fs.unlink(it.path, err=> {
        }));
        delete this.fileList;
    }
}

formidable.IncomingForm.prototype.parsePromise = function (req: http.IncomingMessage) {

    return new Promise((resolve, reject)=> {
        // this.on('error', function (err) {
        //     reject(err);
        // });
        let type = req.headers["content-type"] as string;
        if (!type || type.indexOf("multipart/form-data") < 0) {
            reject(new Error("Content-Type必须为multipart/form-data"));
            return;
        }

        FormParse.processFile(this)
        this.parse(req, (err, fields, files)=> {
            if (err) {
                this.removeFileList();
                reject(err);
                return;
            }

            resolve({fields: fields, files: files});
        });
    });
}


formidable.IncomingForm.prototype.parseModPromise = function (req: Request) {

    return new Promise((resolve, reject)=> {

        let type = req.headers["content-type"] as string;
        if (!type || type.toLowerCase().indexOf("multipart/form-data") < 0) {
            reject(new Error("Content-Type必须为multipart/form-data"));
            return;
        }

        FormParse.processFile(this)
        this.parse(req, (err, fields, files)=> {
            if (err) {
                this.removeFileList();
                reject(err);
                return;
            }

            req.multipart = {
                fields: fields, files: files,
            }


            resolve(this.fieldsMap);
        });
    });
}

export class FormParse {
    fields: Fields;
    files: Files;


    /**
     * form表单数组最大索引限制
     */
    static maxArrayIndex = 100 * 1000;


    /**
     * 解析并验证req请求参数，并将结果传给req[exp.reqFormDat]
     * @param modClass
     * @param req
     * @returns {IFieldRes<any>}
     */
    static async checkReq(modClass: { new(): any;}, req: Request): Promise<IFieldRes<any>> {
        let modInst = new modClass();
        //文件
        let multipart = exp.getMultipart(modClass);
        if (multipart) {
            var form = FormParse.IncomingForm(modInst);
            multipart(form);
            var fromData = await form.parseModPromise(req);
        }
        else {
            fromData = ExpressExt.setReqParas(req);
            form = null as any;
        }

        req[exp.reqFormDat] = fromData;

        let check_data = exp.checkObj(fromData, modInst);
        if (!check_data.ok && form)
            form.removeFileList();

        return check_data;
    }

    static joinFileList(req: Request, fields: Fields, files: Files, modReqObj: any) {
        for (let k in modReqObj) {
            if (modReqObj[k] instanceof exp) {
                delete fields[k];
                fields[k] = files[k] as any;
            }
        }

        return fields;
    }

    static IncomingForm(modReqInst?: any) {
        let f = new formidable.IncomingForm() as formidable.IncomingForm&IncomingFormParse;
        if (modReqInst)
            f.modReqInst = modReqInst;
        return f;
    }

    static getFieldVal(pos: number, name: string, val: any, oldVal: any) {
        var subField: string = "";

        for (; pos < name.length; pos++) {
            var posStr = name[pos];

            if (posStr === ']') {

                if (subField.length == 0) {
                    if (val == null) {
                        val = [];
                    }
                    val.push(FormParse.getFieldVal(pos + 1, name, null, oldVal))
                }
                else {
                    if (sdf.isInt(subField)) {
                        subField = parseInt(subField) as any;
                        if ((subField as any) < 0 || (subField as any) > FormParse.maxArrayIndex) {
                            throw Error(name + "索引超范围:" + subField)
                        }
                        if (val == null)
                            val = [];
                    }
                    else {
                        if (val == null)
                            val = {};
                    }
                    val[subField] = FormParse.getFieldVal(pos + 1, name, val[subField], oldVal);
                }

                return val;
            }


            if (posStr === ' ' || posStr === `'` || posStr === `"` || posStr === `[` || posStr === `\t`) {
                continue;
            }

            subField += posStr;

        }


        return oldVal;
    }

    static processFile(form: formidable.IncomingForm&IncomingFormParse) {
        form.fileList = [];
        form.fieldsMap = {};
        let sizeCount = 0;
        let isFile = false;

        form.onPart = function (part) {

            part.on('data', (buf: Buffer)=> {
                if (isFile) {
                    sizeCount += buf.length;
                    if (form.maxFileSize && sizeCount > form.maxFileSize) {
                        // throw new Error("文件不能大于:" + form.maxFileSize);
                        form.emit("error", new Error("文件不能大于:" + form.maxFileSize))
                    }
                }
            });

            try {
                form.handlePart(part);
            } catch (e) {
                form.emit("error", e)
            }
        }

        form.on('field', function (name: string, value: string) {
            try {
                let pos = name.indexOf("[");
                if (pos > 0) {
                    var fName = name.substr(0, pos);
                    if (!(form.modReqInst && exp.isFile(form.modReqInst[fName]))) {//排除文件
                        form.fieldsMap[fName] = FormParse.getFieldVal(pos + 1, name, form.fieldsMap[fName], value);
                    }
                }
                else {
                    if (!(form.modReqInst && exp.isFile(form.modReqInst[name]))) {//排除文件
                        form.fieldsMap[name] = value;
                    }
                }
            } catch (e) {
                Log.write("field name error:" + name, e);
            }
        });

        form.on('file', function (name: string, file: FileMultiple) {
            isFile = false;
            try {
                let pos = name.indexOf("[");
                if (pos > 0) {
                    var fName = name.substr(0, pos);
                    form.fieldsMap[fName] = FormParse.getFieldVal(pos + 1, name, form.fieldsMap[fName], file);
                }
                else {
                    form.fieldsMap[name] = file;
                }
            } catch (e) {
                Log.write("file name error:" + name, e);
            }
        });

        form.on('fileBegin', function (name: string, file: FileMultiple) {
            sizeCount = 0;
            isFile = true;
            if (form.fileList)
                form.fileList.push(file)

            if (!form.disableStrict && form.modReqInst) {

                let fieldName = name;
                let pos = name.indexOf("[");
                if (pos >= 0) {
                    fieldName = name.substr(0, pos);
                }

                if (!exp.isFile(form.modReqInst[fieldName])) {
                    throw new Error("错误的文件name:" + fieldName);
                    // form.emit("error", new Error("错误的文件name:" + name))
                    // return;
                }
            }

            if (form.checkFile) {
                if (!form.checkFile(name, file)) {
                    throw new Error("无效文件!");
                }
            }

        });

    }

}